#pragma once

#include <wininet.h>
#include <functional>
#include <gtl/mpl/fun.h>
#include <gtl/io/file.h>
#include <gtl/string/str.h>
#include <gtl/thread/thread.h>
#include <gtl/thread/callback.h>
#include <gtl/container/array.h>

#pragma comment(lib, "wininet.lib")

namespace gtl
{
	
class http
{
public:
	http()
	{
		m_file = NULL;
		m_session = InternetOpen(NULL, PRE_CONFIG_INTERNET_ACCESS, NULL, NULL, 0);
	}

	virtual ~http()
	{
		InternetCloseHandle(m_file);
		InternetCloseHandle(m_session);
	}

protected:
	int query(DWORD info)
	{
		tchar value[64] = {0};
		DWORD len = sizeof(value) / sizeof(value[0]);
		HttpQueryInfo(m_file, info, value, &len, NULL);
		return _ttoi(value);
	}

public:
	bool open(const tchar* url)
	{
		if(m_session == NULL || url == NULL)
			return false;

		DWORD flags = INTERNET_FLAG_TRANSFER_BINARY | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_RELOAD;
		m_file = InternetOpenUrl(m_session, url, NULL, 0, flags, 1);
		if(m_file != NULL)
		{			
			int code = query(HTTP_QUERY_STATUS_CODE);
			if(code == 404)
				return false;
		}

		return m_file != NULL;
	}

	DWORD length()
	{
		if(m_file == NULL)
			return 0;

		return query(HTTP_QUERY_CONTENT_LENGTH);
	}

	DWORD read(void* data, int len)
	{
		if(m_file == NULL || data == NULL)
			return 0;

		DWORD bytes = 0;
		InternetReadFile(m_file, data, len, &bytes);
		return bytes;
	}

public:
	void read(const tchar* url, const std::function<void (const char* data, int len, const tchar* url)>& callback)
	{
		return read(&url, 1, callback);
	}

	void read(const tchar** urls, int count, const std::function<void (const char* data, int len, const tchar* url)>& callback)
	{
		if(urls == NULL || count <= 0 || callback == NULL)
			return;

		std::vector<gtl::tstr> uls;
		for(int i = 0; i < count; ++i)
		{
			uls.push_back(urls[i]);
		}

		return read(uls, callback);
	}

	void read(const std::vector<gtl::tstr>& urls, const std::function<void (const char* data, int len, const tchar* url)>& callback)
	{
		m_thread.start([=]() -> unsigned long {
			int len = 0;
			const int kBuffSize = 8192;
			gtl::array<char> data(kBuffSize);

			gtl::tstr url;
			for(size_t i = 0; i < urls.size(); ++i)
			{
				if(this->open(urls[i]))
				{
					url = urls[i];
					char buff[kBuffSize] = {0};
					while(true)
					{
						int ret = this->read(buff, sizeof(buff) / sizeof(buff[0]));
						if(ret <= 0)
							break;

						if(len + ret > data.size())
							data.resize(data.size() + kBuffSize);

						data.copy(len, buff, ret);
						len += ret;
					}

					break;
				}
			}

			std::function<void (const char*, int, const tchar*)> callbk = callback;
			this->m_read_callback.call(data, len, url, [callbk](const char* data, int len, const tchar* url) {
				callbk(data, len, url);
			});

			return 'read';
		});
	}

	void download(const tchar* url, const tchar* path, const std::function<void (const tchar* path, int readsize, int filesize, int index, int count)>& callback)
	{
		return download(&url, &path, 1, callback);
	}

	void download(const tchar** urls, const tchar** paths, int count, const std::function<void (const tchar* path, int readsize, int filesize, int index, int count)>& callback)
	{
		if(urls == NULL || paths == NULL || count <= 0 || callback == NULL)
			return;

		std::vector<gtl::tstr> uls;
		for(int i = 0; i < count; ++i)
		{
			uls.push_back(urls[i]);
		}

		std::vector<gtl::tstr> pths;
		for(int i = 0; i < count; ++i)
		{
			pths.push_back(paths[i]);
		}

		return download(uls, pths, callback);
	}

	void download(const std::vector<gtl::tstr>& urls, const std::vector<gtl::tstr>& paths, const std::function<void (const tchar* path, int readsize, int filesize, int index, int count)>& callback)
	{
		m_thread.start([=]() -> unsigned long {
			std::function<void (const tchar*, int, int, int, int)> callbk = callback;

			size_t count = urls.size();
			for(size_t i = 0; i < count; ++i)
			{
				const gtl::tstr& path = paths[i];
				if(!this->open(urls[i]))
				{
					this->m_download_callback.call(path, -1, -1, i + 1, count, [callbk](const tchar* path, int readsize, int filesize, int index, int count) {
						callbk(path, readsize, filesize, index, count);
					});

					continue;
				}

				int filesize = this->length();
				if(filesize <= 0)
				{
					this->m_download_callback.call(path, 0, 0, i + 1, count, [callbk](const tchar* path, int readsize, int filesize, int index, int count) {
						callbk(path, readsize, filesize, index, count);
					});

					continue;
				}

				gtl::file file;
				if(!file.open(path, _T("wb")))
				{
					this->m_download_callback.call(path, -1, 0, i + 1, count, [callbk](const tchar* path, int readsize, int filesize, int index, int count) {
						callbk(path, readsize, filesize, index, count);
					});

					continue;
				}

				int readsize = 0;
				char data[8 * 1024] = {0};
				while(true)
				{
					int ret = this->read(data, sizeof(data) / sizeof(data[0]));
					if(ret <= 0)
						break;

					readsize += ret;
					file.write(data, ret);

					this->m_download_callback.call(path, readsize, filesize, i + 1, count, [callbk](const tchar* path, int readsize, int filesize, int index, int count) {
						callbk(path, readsize, filesize, index, count);
					});
				}
			}

			return 'down';
		});
	}

protected:
	HINTERNET m_file;
	HINTERNET m_session;
	gtl::thread<> m_thread;
	gtl::callback<gtl::mpl::fun<void (const char*, int, const tchar*)>> m_read_callback;
	gtl::callback<gtl::mpl::fun<void (const tchar*, int, int, int, int)>> m_download_callback;
};

}